package com.ruoyi.file.controller;

import com.ruoyi.common.core.utils.StringUtils;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.compress.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.file.FileUtils;
import com.ruoyi.file.service.ISysFileService;
import com.ruoyi.system.api.domain.SysFile;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 文件请求处理
 *
 * @author ruoyi
 */
@RestController
public class SysFileController {
    private static final Logger log = LoggerFactory.getLogger(SysFileController.class);

    @Autowired
    private ISysFileService sysFileService;

    @Autowired
    private ThreadPoolTaskExecutor executor;

    /**
     * 文件上传请求
     */
    @PostMapping("upload")
    public R<SysFile> upload(MultipartFile file) {
        try {
            // 上传并返回访问地址
            String url = sysFileService.uploadFile(file);
            SysFile sysFile = new SysFile();
            sysFile.setName(FileUtils.getName(url));
            sysFile.setUrl(url);
            return R.ok(sysFile);
        } catch (Exception e) {
            log.error("上传文件失败", e);
            return R.fail(e.getMessage());
        }
    }

    /**
     * 批量文件上传请求，如对一些图片，小文件
     *
     * @param files
     * @return
     */
    @ApiOperation(value = "批量上传文件", httpMethod = "POST")
    @PostMapping(value = "batchUpload", headers = "content-type=multipart/form-data")
    public R<List<SysFile>> batchUpload(@ApiParam(value = "文件", required = true) @RequestPart("files") MultipartFile[] files) {
        //单线程串行上传
        List<SysFile> result = new ArrayList<>();
        try {
            if (files != null && files.length > 0) {
                long start = System.currentTimeMillis();
                for (MultipartFile file : files) {
                    // 上传并返回访问地址
                    String url = sysFileService.uploadFile(file);
                    if (StringUtils.isBlank(url)) {
                        continue;
                    }
                    SysFile sysFile = new SysFile();
                    sysFile.setName(FileUtils.getName(url));
                    sysFile.setUrl(url);
                    result.add(sysFile);
                }
                long end = System.currentTimeMillis();
                log.error("总耗时：" + (end - start) + " 毫秒");
            }
            return R.ok(result);
        } catch (Exception e) {
            log.error("上传文件失败", e);
            return R.fail(e.getMessage());
        }
    }

    /**
     * 批量文件上传请求，如对一些图片，小文件
     *
     * @param files
     * @return
     */
    @ApiOperation(value = "批量上传文件", httpMethod = "POST")
    @PostMapping(value = "batchUploadParallel", headers = "content-type=multipart/form-data")
    public R<List<SysFile>> batchUploadParallel(@ApiParam(value = "文件", required = true) @RequestPart("files") MultipartFile[] files)
            throws InterruptedException, ExecutionException {
        if (files == null || files.length == 0) {
            log.warn("没有要上传的文件");
            return R.ok();
        }

        long start = System.currentTimeMillis();
        //多线程并行上传 有线程安全问题
        //https://blog.csdn.net/Ascend1977/article/details/130845357
//        List<SysFile> uploadedFiles = Arrays.stream(files).parallel().map(file -> {
//            String url = null;
//            try {
//                url = sysFileService.uploadFile(file);
//            } catch (Exception e) {
//                SysFile sysFile = new SysFile();
//                String fileName = new String(file.getOriginalFilename().getBytes(), StandardCharsets.UTF_8);
//                sysFile.setName(fileName);
//                return sysFile;
//            }
//
//            SysFile sysFile = new SysFile();
//            String fileName = new String(file.getOriginalFilename().getBytes(), StandardCharsets.UTF_8);
//            sysFile.setName(fileName);
//            sysFile.setUrl(url);
//            return sysFile;
//        }).collect(Collectors.toList());

        // 创建线程池 创建CompletionService
        CompletionService<SysFile> completionService = new ExecutorCompletionService<>(executor);

        List<SysFile> uploadedFiles = new ArrayList<>();
        for (int i = 0; i < files.length; i++) {
            int finalI = i;
            completionService.submit(() -> {
                log.info("start executeAsync " + finalI);
                String url = null;
                try {
                    url = sysFileService.uploadFile(files[finalI]);
                } catch (Exception e) {
                    SysFile sysFile = new SysFile();
                    String fileName = new String(files[finalI].getOriginalFilename().getBytes(), StandardCharsets.UTF_8);
                    sysFile.setName(fileName);
                    return sysFile;
                }

                SysFile sysFile = new SysFile();
                String fileName = new String(files[finalI].getOriginalFilename().getBytes(), StandardCharsets.UTF_8);
                sysFile.setName(fileName);
                sysFile.setUrl(url);
                log.info("end executeAsync" + finalI);
                return sysFile;
            });
        }
        for (int i = 0; i < files.length; i++) {
            SysFile sysFile = completionService.take().get();
            uploadedFiles.add(sysFile);
        }
        long end = System.currentTimeMillis();
        log.error("总耗时：" + (end - start) + " 毫秒");

        return R.ok(uploadedFiles);
    }

    /**
     * get下载
     *
     * @param filePath
     * @param fileName
     * @param response
     * @throws IOException
     */
    @GetMapping("/download")
    public void download(String filePath, String fileName, HttpServletResponse response) throws IOException {

        // 获得文件输入流
        InputStream inStream = sysFileService.downloadFile(filePath);
        // 设置文件下载方式：附件下载
        response.setHeader("content-disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));
        // 获取响应输出流
        ServletOutputStream outputStream = response.getOutputStream();
        // 下载文件 文件的输入流 复制 到response输出流
        IOUtils.copy(inStream, response.getOutputStream());
        IOUtils.closeQuietly(inStream);
        IOUtils.closeQuietly(outputStream);
    }

    /**
     * addResourceHandlers 可以替代了
     *
     * @param response
     * @param fileName
     * @param filePath
     * @throws FileNotFoundException
     */
    @ApiOperation(value = "文件下载", httpMethod = "POST")
    @PostMapping("/download")
    public void download(HttpServletResponse response, String fileName, String filePath) throws FileNotFoundException {
        // 读到流中
        //InputStream inStream = new FileInputStream(filePath);// 文件的存放路径
        InputStream inStream = sysFileService.downloadFile(filePath);
        // 设置输出的格式
        response.reset();
        response.setContentType("application/x-msdownload");
        response.setContentType("application/octet-stream");
        response.addHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");

        try {
            response.addHeader("Content-Length", String.valueOf(inStream.available()));
            OutputStream os = response.getOutputStream();
            byte[] buff = new byte[1024];
            for (int len = inStream.read(buff); len > 0; len = inStream.read(buff)) {
                os.write(buff);
                os.flush();
            }
            inStream.close();
            os.close();
        } catch (IOException e) {
            log.error("文件下载失败", e);
        }
    }

    /**
     * addResourceHandlers 可以替代了
     *
     * @param response
     * @param filePath
     * @return
     * @throws FileNotFoundException
     */
    @ApiOperation(value = "文件下载流 image/jpeg、png", httpMethod = "POST")
    @PostMapping("/downloadStream")
    public String downloadStream(HttpServletResponse response, @RequestBody String filePath) throws FileNotFoundException {
        try {
            if (filePath.endsWith("png")) {
                return "data:image/png;base64," + convertImageToBase64(filePath, 200);
            } else {
                return "data:image/jpeg;base64," + convertImageToBase64(filePath, 200);
            }
        } catch (Exception e) {
            log.error("文件下载失败", e);
            return "";
        }
    }

    /**
     * 将url压缩为指定大小
     *
     * @param imageUrl
     * @param sizeLimit
     * @return
     * @throws IOException
     */
    public static String convertImageToBase64(String imageUrl, Integer sizeLimit) throws IOException {
        String imgType = imageUrl.substring(imageUrl.lastIndexOf(".") + 1);
        //默认上限为500k
        if (sizeLimit == null) {
            sizeLimit = 500;
        }
        sizeLimit = sizeLimit * 1024;
        String base64Image;
        DataInputStream dataInputStream = null;
        ByteArrayOutputStream outputStream = null;
        ByteArrayInputStream inputStream = null;
        try {
            //从远程读取图片
            InputStream inStream = new FileInputStream(imageUrl);// 文件的存放路径
            dataInputStream = new DataInputStream(inStream);

            outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int length;
            while ((length = dataInputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
            byte[] context = outputStream.toByteArray();
            //将图片数据还原为图片
            inputStream = new ByteArrayInputStream(context);
            //注意：使用ImageIO.read（）方法可能会导致图片数据丢失，导致图片变色
            BufferedImage image = ImageIO.read(inputStream);

            int imageSize = context.length;
            int type = image.getType();
            int height = image.getHeight();
            int width = image.getWidth();
            BufferedImage tempImage;
            //判断文件大小是否大于size,循环压缩，直到大小小于给定的值
            while (imageSize > sizeLimit) {
                //将图片长宽压缩到原来的90%
                height = new Double(height * 0.6).intValue();
                width = new Double(width * 0.6).intValue();
                tempImage = new BufferedImage(width, height, type);
                // 绘制缩小后的图
                tempImage.getGraphics().drawImage(image, 0, 0, width, height, null);
                //重新计算图片大小
                outputStream.reset();
                ImageIO.write(tempImage, imgType, outputStream);
                imageSize = outputStream.toByteArray().length;
            }

            //将图片转化为base64并返回
            byte[] data = outputStream.toByteArray();
            //此处一定要使用org.apache.tomcat.util.codec.binary.Base64，防止再linux上出现换行等特殊符号
            base64Image = Base64.encodeBase64String(data);
        } catch (Exception e) {
            //抛出异常
            throw e;
        } finally {
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return base64Image;
    }
}